package lsp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;

public class LspConnectionOutPackageHandler implements Runnable {
	private DatagramSocket ds;
	// private LinkedBlockingDeque<LspMessage> entrada;
	private LinkedBlockingDeque<LspMessage> saida;
	private AtomicBoolean epochExpired;
	// private AtomicBoolean epochLimitExpired;
	private InetAddress client;
	private short connId;
	private int port;
	private short lastSentAck = 0;
	private short lastSentMessage = 0;

	public LspConnectionOutPackageHandler(DatagramSocket ds,
			LinkedBlockingDeque<LspMessage> saida, AtomicBoolean epochExpired,
			InetAddress client, short connId, int port) {
		// TODO Auto-generated constructor stub
		this.ds = ds;
		// this.entrada = entrada;
		this.saida = saida;
		this.epochExpired = epochExpired;
		// this.epochLimitExpired = epochLimitExpired;
		this.client = client;
		this.connId = connId;
		this.port = port;
	}

	@Override
	public void run() {
		LspMessage mensagem;
		try {
			while (!Thread.currentThread().isInterrupted()) {
				mensagem = this.saida.peekLast();
				if (this.epochExpired.get()) {
					epochExpiredRotine(mensagem);
					continue;
				}
				if (mensagem != null) {
					/*
					 * if (mensagem.getMsgType() == 0) // Connect
					 * enviaConnect(mensagem); else
					 */
					if (mensagem.getMsgType() == 1) // Dados
						this.enviaData(mensagem);
					else if (mensagem.getMsgType() == 2) // Ack
						this.enviaAck(mensagem);
				}
			}
		} catch (IOException | InterruptedException e) {
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}
	}

	private void enviaConnect(LspMessage mensagem) throws IOException {
		DatagramPacket pacote = new DatagramPacket(mensagem.getMessage(),
				mensagem.length(), this.client, this.port);
		this.ds.send(pacote);
	}

	// private void reEnviaAck(LspMessage mensagem) throws IOException {
	// DatagramPacket pacote = new DatagramPacket(mensagem.getMessage(),
	// mensagem.length(), this.servidor, this.port);
	// this.ds.send(pacote);
	// }

	private void enviaAck(LspMessage mensagem) throws IOException,
			InterruptedException {
		DatagramPacket pacote = new DatagramPacket(mensagem.getMessage(),
				mensagem.length(), this.client, this.port);
		this.ds.send(pacote);
		this.saida.takeLast(); // Remove ACK da pilha de saida
		this.lastSentAck = mensagem.getSeqNumber();
	}

	private void enviaData(LspMessage mensagem) throws IOException {
		DatagramPacket pacote = new DatagramPacket(mensagem.getMessage(),
				mensagem.length(), this.client, this.port);
		if (mensagem.getSeqNumber() == (this.lastSentMessage + 1)) {
			this.ds.send(pacote);
			this.lastSentMessage = mensagem.getSeqNumber();
		}
	}

	private void epochExpiredRotine(LspMessage mensagem) throws IOException,
			InterruptedException {
		if (mensagem == null) { // Pilha vazia, que contempla o caso do Connect
								// confirmado, mas sem mensagens recebidas
			this.saida.putLast(new LspMessage((short) 2, this.connId,
					this.lastSentAck, "".getBytes()));
			enviaAck(this.saida.peekLast());
		} else if (mensagem.getConnId() == 0) {
			enviaConnect(mensagem);
			return;
		} else { // Dados e Ack
			if (mensagem.getMsgType() == 1) {// Dados
				DatagramPacket pacote = new DatagramPacket(
						mensagem.getMessage(), mensagem.length(),
						this.client, this.port);
				this.ds.send(pacote);
			} else if (mensagem.getMsgType() == 2) // Ack
				enviaAck(mensagem);
		}
		this.epochExpired.set(false);
	}
}
